<template>
  <div class="editor">
    <div class="editor-container">
      <draggable
        v-model="rootNode"
        :id="rootNode.id"
        class="vc-page"
        :style="visual.parseStyle(rootNode.currStyle)"
        @click.stop="activeNode(rootNode)"
        @mouseoverNode="mouseoverNode"
        @activeNode="activeNode"
        @deleteNode="deleteNode"
      />
    </div>
    <div class="editor-tool">
      <ac-visual-tool
        ref="visualTool"
        :type="layoutMode"
        @preview="preview"
        @save="save"
        @quit="quit"
        @change="changeConfig"
        @activeNode="activeNode"
      />
    </div>

    <div
      v-if="tipsNodeTree.length > 0"
      class="active-box"
      :style="tipsBoxStyle"
    >
      <div class="tips-box">
        <div class="tips-operate">
          <div class="tips-name" @click="showTipsInput">
          <span
            v-if="!tipsBoxEdit"
            class="tips-name-txt"
          >{{activatedNode.name}}</span>
            <el-input
              v-else
              ref="tipsBoxInput"
              v-model="activatedNode.name"
              size="small"
              @keyup.enter="tipsBoxEdit = false"
              @blur="tipsBoxEdit = false"
            />
          </div>
          <div class="tips-copy" title="复制" @click="copyNode(activatedNode)">
            <ac-icon iconId="018dc9c3efb82f31c161" />
          </div>
          <div class="tips-tpl" title="保存到模板" @click="saveTplNode(activatedNode)">
            <ac-icon iconId="018ece4f68eaa77e4813" />
          </div>
          <div class="tips-delete" title="删除" @click="deleteNode(activatedNode)">
            <ac-icon iconId="018dc9c3efd3a0502886" />
          </div>
        </div>
        <div class="tips-node">
          <div
            v-for="(item, index) in tipsNodeTree"
            :key="index"
            @click="activeNode(item)"
            class="tips-node-item"
          >{{item.name}}</div>
        </div>
      </div>
    </div>

    <div class="mouseover-box" :style="moveBoxStyle">
      <div class="tips-box">{{moveBoxName}}</div>
    </div>
  </div>

  <!--页面预览-->
  <el-dialog
    v-model="previewVisible"
    title="预览"
    :close-on-click-modal="false"
    class="no-padding"
    append-to-body
    destroy-on-close
    fullscreen
    draggable
  >
    <div style="height: 100%;display: flex;flex-direction: column">
      <PreviewPage :visualJson="rootNode" />
    </div>
  </el-dialog>
</template>

<script setup>
import { ref, onMounted, nextTick, provide, watch } from 'vue';
import { ElMessage, ElMessageBox } from "element-plus";
import AcVisualTool from "@/admin/visual/tool.vue";
import visual from "@/visual/js/visual";
import tool from "@/utils/tool";
import Draggable from "@/admin/visual/draggable.vue";
import PreviewPage from "@/visual/view/page.vue";
import api from "@/http/api";
import { useAdminVisualCompStore } from "@/admin/visual/js/visual-comp";
import { components } from "@/admin/visual/js/component.config";

const emit = defineEmits(['save', 'quit']);
const props = defineProps({
  layoutMode: String,
  paramList: {
    type: Array,
    default: () => []
  },
  json: String,
});

const initializing = ref(true);
const compDataList = ref([]);
const visualComp = useAdminVisualCompStore();
const visualTool = ref();
const unSave = ref(false);
const rootNode = ref({});
const activatedNode = ref({});
const tipsBoxStyle = ref('');
const tipsBoxInput = ref();
const tipsBoxEdit = ref(false);
const tipsNodeTree = ref([]);

const moveBoxName = ref('');
const moveBoxStyle = ref('');

const previewVisible = ref(false);
const pageParamList = ref(tool.deepClone(props.paramList));
const nameIndex = ref({});

provide('rootNode', rootNode);
provide('activatedNode', activatedNode);
provide('pageParamList', pageParamList);
provide('nameIndex', nameIndex);

const activeNode = async(node) => {
  activatedNode.value = node;
  tipsNodeTree.value = getAllParents(node.id, rootNode.value);
  await initNodeData(activatedNode.value);
};

const showTipsInput = () => {
  tipsBoxEdit.value = true;
  nextTick(() => {
    tipsBoxInput.value && tipsBoxInput.value.focus();
  });
};

const showTipsBox = () => {
  if (activatedNode.value && activatedNode.value.id && activatedNode.value.type !== 'page') {
    const element = document.getElementById(activatedNode.value.id);
    if (element) {
      const rect = element.getBoundingClientRect();
      tipsBoxStyle.value = `
        display: block;
        width: ${rect.width}px;
        height: ${rect.height}px;
        top: ${rect.top}px;
        left: ${rect.left}px;`;
    }
  } else {
    tipsBoxStyle.value = '';
  }
};

const copyNode = (node) => {
  const parentNode = getParentNode(node.id, rootNode.value);
  if (!parentNode) return;

  const idMap = {};
  const newNode = tool.deepClone(node);
  tool.walkProp(newNode, (key, obj) => {
    if (key === 'id') {
      const newId = tool.uuids();
      if (obj[key]) {
        idMap[obj[key]] = newId;
      }
      obj[key] = newId;
    } else if (key === 'name') {
      if (obj[key].indexOf("-") > 0) {
        const name = obj[key].replace(/\d+$/, '');
        obj[key] = getNodeName(name);
      }
    }
  });
  tool.walkProp(newNode, (key, obj) => {
    if (obj[key] && typeof obj[key] === 'string' && idMap[obj[key]]) {
      obj[key] = idMap[obj[key]];
    }
  });
  parentNode.children.push(newNode);
  activeNode(newNode);
};

const saveTplNode = (node) => {
  const json = JSON.stringify(node);
  api.post("/system/pageVisualTemplate/save", {type: '普通', name: node.name || '', json: json}).then(res => {
    if (res.code === '1') {
      ElMessage.success("保存模板成功！");
      visualTool.value.loadVisualTpl();
    }
  });
};

const buildNameIndex = () => {
  const regex = new RegExp('-\\d+$');
  tool.walkTree(rootNode.value, node => {
    if (node.name && node.name.match(regex)) {
      const lastIndex = node.name.lastIndexOf('-');
      const name = node.name.substring(0, lastIndex);
      const maxIndex = parseInt(node.name.substring(lastIndex + 1));
      if (!nameIndex.value[name] || nameIndex.value[name] < maxIndex) {
        nameIndex.value[name] = maxIndex;
      }
    }
  });
};

const getNodeName = (name) => {
  if (!name) return '组件-01';

  if (name.endsWith('-')) {
    name = name.substr(0, name.length - 1);
  }

  if (!nameIndex.value[name]) {
    buildNameIndex();
  }

  if (!nameIndex.value[name]) {
    nameIndex.value[name] = 0;
  }

  let index = ++nameIndex.value[name];
  return name + '-' + (index < 10 ? '0' : '') + index;
};

const deleteNode = (node) => {
  if (typeof node === 'undefined' || node == null) {
    node = activatedNode.value;
  }

  let nodeName;
  if (!node.name) {
    nodeName = '此项'
  } else {
    nodeName = '【' + node.name + '】';
  }
  ElMessageBox.confirm('请确认是否删除' + nodeName + '？', '提示', {type: 'warning'}).then(() => {
    doDeleteNode(node, rootNode.value.children || []);
    tipsBoxStyle.value = '';
    activatedNode.value = {compData: {}};
  }).catch(()=>{});
};

const doDeleteNode = (node, list) => {
  if (typeof node === 'undefined' || node == null
      || typeof list === 'undefined' || list === null || list.length === 0) {
    return false;
  }

  for (let i=0; i<list.length; i++) {
    if (list[i].id === node.id) {
      list.splice(i, 1);
      return true;
    }

    if (doDeleteNode(node, list[i].children || [])) {
      return true;
    }
  }
};

const mouseoverNode = (item) => {
  if (!item) {
    moveBoxStyle.value = '';
    moveBoxName.value = '';
  } else {
    const element = document.getElementById(item.id);
    if (element) {
      const rect = element.getBoundingClientRect();
      moveBoxStyle.value = `
        display: block;
        width: ${rect.width}px;
        height: ${rect.height}px;
        top: ${rect.top}px;
        left: ${rect.left}px;`;
      moveBoxName.value = item.name;
    }
  }
};

const getAllParents = (nodeId, tree) => {
  const parents = [];

  function findParents(id, tree) {
    if (id === tree.id) {
      return true;
    }

    for (let children of tree.children || []) {
      if (findParents(id, children)) {
        if (tree.builtin !== true) {
          parents.push(tree);
        }
        return true;
      }
    }

    return false;
  }

  findParents(nodeId, tree);
  return parents;
};

const getParentNode = (nodeId, node) => {
  for (let children of node.children || []) {
    if (children.id === nodeId) {
      return node;
    }

    const parentNode = getParentNode(nodeId, children);
    if (parentNode != null) {
      return parentNode;
    }
  }

  return null;
};

const preview = () => {
  previewVisible.value = true;
};

const save = () => {
  const json = tool.deepClone(
    rootNode.value,
    prop => prop !== 'currStyle' && prop !== 'currCompStyle' && prop !== 'dataList'
  );

  const buttonList = [];
  tool.walkTree(json, (node) => {
    if (node.type === 'button'
        && node.styleList
        && node.styleList.length > 0
        && node.styleList[0].compStyle.permit === true) {
      buttonList.push({id: node.id, name: node.name});
    }
  });
  emit('save', JSON.stringify(json), buttonList);
  unSave.value = false;
};

const quit = () => {
  if (unSave.value) {
    ElMessageBox.confirm('修改未保存是否退出？', '提示', {type: 'warning'}).then(() => {
      emit('quit');
    }).catch(() => {});
  } else {
    emit('quit');
  }
};

const changeConfig = () => {
  if (!initializing.value) {
    unSave.value = true;
  }
};

const init = () => {
  initializing.value = true;
  if (props.json) {
    rootNode.value = JSON.parse(props.json);
    if (!rootNode.value.currStyle
        && rootNode.value.styleList
        && rootNode.value.styleList.length > 0) {
      rootNode.value.currStyle = rootNode.value.styleList[0].style || {};
    }
    rootNode.value.compData.inputData = true;
  } else {
    rootNode.value = visual.createNode('页面', {type: 'page'});
    rootNode.value.compData.inputData = true;
  }
  activeNode(rootNode.value);
  nextTick(() => {
    initializing.value = false;
  });
};

const initNodeData = async(node) => {
  const data = await getCompData(node.type) || {};
  node.compData.inputData = data.inputData || node.compData.inputData || false;
  if (!node.compData.dataJson) {
    node.compData.dataJson = JSON.stringify(data.dataList || []);
  }

  const fieldList = node.compData.fieldList || [];
  if (fieldList.length === 0) {
    (data.fieldList || '').split(',').forEach(fieldName => {
      if (fieldName) {
        fieldList.push({
          id: tool.uuids(),
          name: fieldName,
          value: fieldName,
          builtin: !!data.fieldBuiltIn
        });
      }
    });
  }
  node.compData.fieldList = fieldList;
};

const loadData = async() => {
  const dataList = (await visual.loadData(rootNode.value.compData, {}, {}, rootNode.value)).data || [];
  rootNode.value.compData.dataList = JSON.stringify(dataList);

  (rootNode.value.compData.fieldList || []).forEach(field => {
    const find = pageParamList.value.find(param => param.id === field.id);
    if (!find) {
      pageParamList.value.push(field);
    }
  });
};

const createCompData = (data, children) => {
  (children || []).forEach(item => {
    if (item.type && item.inputData) {
      data[item.type] = {
        inputData: item.inputData,
        fieldList: item.fieldList || '',
        fieldBuiltIn: !!item.fieldBuiltIn,
        dataList: item.dataList || []
      };
    }
    createCompData(data, item.children);
  });
};

const getCompData = async(type) => {
  if (type) {
    if (!compDataList.value[type]) {
      const comp = await visualComp.getVisualComp(type);
      compDataList.value[type] = comp || {};
    }
    return tool.deepClone(compDataList.value[type]);
  } else {
    return {};
  }
};

watch(() => rootNode.value.compData?.refresh, () => {
  loadData();
});

onMounted(() => {
  init();
  loadData();
  createCompData(compDataList.value, components);
  setInterval(showTipsBox, 200);
});
</script>

<style scoped lang="scss">
.editor {
  height: 100%;
  display: flex;
  flex-direction: row;
  position: relative;
  .editor-container {
    flex-grow: 1;
    display: flex;
    flex-direction: column;
    overflow: auto;
    background-color: #f0f2f5;
    .vc-page {
      width: 100%;
      height: 100%;
      overflow: auto;
      box-sizing: border-box;
      font-family: "Microsoft YaHei", "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", Arial, sans-serif;
      :deep(.empty) {
        width: 100%;
        height: 100px;
        box-sizing: border-box;
        box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.3);
        outline: 1px dashed #333 !important;
        outline-offset: -2px !important;
      }
    }
    .vc-page:after {
      content: '';
      height: 50px;
      display: block;
    }
  }
  .editor-tool {
    z-index: 100;
    width: 370px;
    flex-shrink: 0;
  }
}
.active-box {
  display: none;
  position: absolute;
  border: 1px solid #0085f6;
  box-sizing: border-box;
  pointer-events: none;
  .tips-box {
    position: relative;
    top: calc(100% + 3px);
    pointer-events: auto;
    display: flex;
    flex-direction: column;
    width: fit-content;
    .tips-operate {
      display: inline-flex;
      flex-direction: row;
      align-items: center;
      .tips-name, .tips-copy, .tips-tpl, .tips-delete {
        border-radius: 3px;
        height: 25px;
        font-size: 15px;
        margin-right: 2px;
        color: white;
        display: flex;
        cursor: pointer;
        flex-direction: column;
        justify-content: center;
      }

      .tips-name {
        padding: 3px 5px;
        background: #409EFF;
        .tips-name-txt {
          white-space: nowrap;
        }
        :deep(.el-input) {
          min-width: 200px;
        }
      }

      .tips-copy {
        background: #67C23A;
        padding: 3px 8px;
      }

      .tips-tpl {
        background: #ff8000;
        padding: 3px 8px;
      }

      .tips-delete {
        background: #F56C6C;
        padding: 3px 8px;
      }
    }
    .tips-node {
      display: none;
      flex-direction: column;
      width: fit-content;
      .tips-node-item {
        white-space: nowrap;
        border-radius: 3px;
        margin-top: 1px;
        padding: 3px 5px;
        height: 25px;
        font-size: 15px;
        color: white;
        display: flex;
        cursor: pointer;
        flex-direction: column;
        justify-content: center;
        background: #409EFF;
      }
    }
  }
  .tips-box:has(.tips-name:hover) .tips-node, .tips-box .tips-node:hover{
    display: inline-flex;
  }
}
.mouseover-box {
  display: none;
  position: absolute;
  border: 1px solid #0085f6;
  box-sizing: border-box;
  pointer-events: none;
  .tips-box {
    position: relative;
    top: calc(100% + 3px);
    text-align: center;
    pointer-events: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    color: rgb(33, 138, 246);
  }
}
</style>